Skip to content

Conversation

@dunglas
Copy link
Member

@dunglas dunglas commented Jan 9, 2026

Closes #83 #880 #1286.

Working patch for Windows support.

Supports linking to the official PHP release (TS version).
Includes some work from #1286 (thanks @TenHian!!)

This patch allows using Visual Studio to compile the cgo code. To do so, it must be compiled with Go 1.26 (RC) with the following setup:

winget install -e --id Microsoft.VisualStudio.2022.Community --override "--passive --wait --add Microsoft.VisualStudio.Workload.NativeDesktop --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --includeRecommended"
winget install -e --id GoLang.Go

$env:PATH += ';C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin'

cd c:\
gh repo clone microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
.\vcpkg\vcpkg install pthreads brotli

# build watcher
Invoke-WebRequest -Uri "https://github.com/e-dant/watcher/releases/download/0.14.3/x86_64-pc-windows-msvc.tar" -OutFile "$env:TEMP\watcher.tar"
tar -xf "$env:TEMP\watcher.tar" -C C:\
Rename-Item -Path "C:\x86_64-pc-windows-msvc" -NewName "watcher-x86_64-pc-windows-msvc"
Remove-Item "$env:TEMP\watcher.tar"

# download php
Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php.zip"
Expand-Archive -Path "$env:TEMP\php.zip" -DestinationPath "C:\"
Remove-Item "$env:TEMP\php.zip"

# download php development package
Invoke-WebRequest -Uri "https://downloads.php.net/~windows/releases/archives/php-devel-pack-8.5.1-Win32-vs17-x64.zip" -OutFile "$env:TEMP\php-devel.zip"
Expand-Archive -Path "$env:TEMP\php-devel.zip" -DestinationPath "C:\"
Remove-Item "$env:TEMP\php-devel.zip"

$env:GOTOOLCHAIN = 'go1.26rc1'
$env:CC = 'clang'
$env:CXX = 'clang++'
$env:CGO_CFLAGS = "-I$env:C:\vcpkg\installed\x64-windows\include -IC:\watcher-x86_64-pc-windows-msvc -IC:\php-8.5.1-devel-vs17-x64\include -IC:\php-8.5.1-devel-vs17-x64\include\main -IC:\php-8.5.1-devel-vs17-x64\include\TSRM -IC:\php-8.5.1-devel-vs17-x64\include\Zend -IC:\php-8.5.1-devel-vs17-x64\include\ext"
$env:CGO_LDFLAGS = '-LC:\vcpkg\installed\x64-windows\lib -lbrotlienc -LC:\watcher-x86_64-pc-windows-msvc -llibwatcher-c -LC:\php-8.5.1-Win32-vs17-x64 -LC:\php-8.5.1-devel-vs17-x64\lib -lphp8ts -lphp8embed'

# clone frankenphp and build
git clone -b windows https://github.com/php/frankenphp.git
cd frankenphp\caddy\frankenphp
go build -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx

# Tests

$env:PATH += ";$env:VCPKG_ROOT\installed\x64-windows\bin;C:\watcher-x86_64-pc-windows-msvc";C:\php-8.5.1-Win32-vs17-x64"
"opcache.enable=0`r`nopcache.enable_cli=0" | Out-File -Encoding ascii php.ini
$env:PHPRC = Get-Location
go test -ldflags '-extldflags="-fuse-ld=lld"' -tags nowatcher,nobadger,nomysql,nopgx .

TODO:

  • Fix remaining skipped tests (scaling and watcher)
  • Test if the watcher mode works as expected
  • Automate the build with GitHub Actions

@henderkes
Copy link
Contributor

Very interesting, I had no idea that lld could link MinGW and MSVC objects together. This links against php8embed.lib?

@dunglas
Copy link
Member Author

dunglas commented Jan 9, 2026

Mingw isn't used at all with this patch. Everything uses Visual Studio.
For cgo, it uses the clang version provided by VS, which has a VS backend (needs Go 1.26, I stumbled upon this undocumented Google patch very recently).

php8embed.dll is not even needed, only php8ts.dll is needed, because I disabled the php-cli subcommand.

Locally, I even manage to run php-cli, but this requires manually compiling PHP to enable the embed SAPI, so I disabled it for now because both php8ts.dll and php.exe are shipped with the official PHP package.

I also have a patch for Static PHP CLI, but it's not working because the PHP source code and Makefile on Windows doesn't support building a static version of php8ts.dll and php8embed.dll. Patching the PHP source code will be necessary.

@henderkes
Copy link
Contributor

For cgo, it uses the clang version provided by VS, which has a VS backend (needs Go 1.26, I stumbled upon this undocumented Google patch very recently).

That's the part I was missing, thank you!

@henderkes
Copy link
Contributor

henderkes commented Jan 10, 2026

Gave it a shot and updated your initial post for instructions, however, once it attempts to serve a php file with .\frankenphp.exe php-server --root=./ the app stops.

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

Have you copied all the necessary DDLs in the same directory?

@henderkes
Copy link
Contributor

Shouldn't be necessary with

$env:PATH += ";$env:VCPKG_ROOT\installed\x64-windows\bin"
$env:PATH += ";C:\watcher-x86_64-pc-windows-msvc"
$env:PATH += ";C:\php-8.5.1-Win32-vs17-x64"

Is anything else required?

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

You must also add $env:PATH += ";C:\php-8.5.1-Win32-vs17-x64\lib".

@henderkes
Copy link
Contributor

Same issue, I don't think it could be related to libraries anyway, as it would fail to run in the first place then. Shared libraries are (by default) loaded at initialisation time and we're not passing delayload arguments to the compilation.

Log:

❯❯ frankenphp git:(windows) 21:46 .\frankenphp.exe php-server --root=./
2026/01/10 20:46:22.912 WARN    admin   admin endpoint disabled
2026/01/10 20:46:22.912 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x2e61d3850500"}
2026/01/10 20:46:22.912 WARN    http.auto_https server is listening only on the HTTP port, so no automatic HTTPS will be applied to this server {"server_name": "php", "http_port": 80}
2026/01/10 20:46:22.949 INFO    frankenphp      FrankenPHP started 🐘   {"php_version": "8.5.1", "num_threads": 64, "max_threads": 64}
2026/01/10 20:46:22.950 WARN    http    HTTP/2 skipped because it requires TLS  {"network": "tcp", "addr": ":80"}
2026/01/10 20:46:22.950 WARN    http    HTTP/3 skipped because it requires TLS  {"network": "tcp", "addr": ":80"}
2026/01/10 20:46:22.950 INFO    http.log        server running  {"name": "php", "protocols": ["h1", "h2", "h3"]}
2026/01/10 20:46:22.951 INFO    Caddy serving PHP app on :80
2026/01/10 20:46:22.953 INFO    tls     storage cleaning happened too recently; skipping for now        {"storage": "FileStorage:C:\\Users\\m\\AppData\\Roaming\\Caddy", "instance": "489ade52-21ab-40c6-b18a-2932fb8eab4d", "try_again": "2026/01/11 20:46:22.953", "try_again_in": 86400}
2026/01/10 20:46:22.953 INFO    tls     finished cleaning storage units
❯❯ frankenphp git:(windows)  21:46

Text files like an index.html page are served just fine. Only when php is attempted to be executed does the program simply stop.

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

I didn't try the php-server command yet, only run with a custom Caddyfile.
Are the tests green? (They are on my local installation)

@dunglas
Copy link
Member Author

dunglas commented Jan 10, 2026

Could you also show me the content of your PHP script?

@henderkes
Copy link
Contributor

Content of the php script:

<?php
echo phpinfo();

I haven't ran the test suite yet, but frankenphp run shows the same behaviour.

@dunglas dunglas marked this pull request as ready for review January 12, 2026 13:43
@dunglas
Copy link
Member Author

dunglas commented Jan 12, 2026

Here is a build I created locally: https://drive.google.com/file/d/1B09de1rERpRUN-bnAje0K2vHcwhVoDJa/view?usp=sharing
It's the official PHP binary distribution with FrankenPHP itself and the extra needed DLLs added.

On my computer, it runs Symfony without issue @henderkes.

@henderkes
Copy link
Contributor

henderkes commented Jan 12, 2026

Thanks, I'll try this one and report back.

Edit: it's working, but it was linked against 8.5.3-dev. I'll try to link against 8.5.1 again and see if I can get anywhere after the new commits.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements Windows support for FrankenPHP, enabling it to compile and run on Windows platforms using Visual Studio's LLVM/Clang toolchain. The changes include platform-specific build configurations, cross-platform path handling improvements, header file reorganization for Windows compatibility, and conditional compilation of features not yet supported on Windows (like CLI mode).

Changes:

  • Added Windows-specific CGO flags, header includes, and preprocessor definitions
  • Improved cross-platform file path handling using filepath.Join and platform-aware separators
  • Moved CLI functionality to Unix-only builds with proper build tags
  • Updated watcher library dependency and adjusted import paths

Reviewed changes

Copilot reviewed 31 out of 34 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frankenphp.h Added Windows-specific headers, defines, and IntSafe function implementations for Clang
frankenphp.c Added Windows-specific conditional compilation for SIGPIPE handling and unistd.h
cgo.go Added Windows-specific CGO CFLAGS and LDFLAGS
cli.go Moved CLI execution functions to Unix-only build with //go:build !windows tag
cli_test.go Moved CLI tests to Unix-only build
frankenphp.go Removed CLI functions (moved to cli.go)
frankenphp_test.go Removed CLI tests (moved to cli_test.go)
watcher_test.go Fixed file path construction to use filepath.Join for cross-platform compatibility
scaling_test.go Updated path construction to use filepath.Join (with bug: leading slash)
phpmainthread_test.go Updated path construction to use filepath.Join
internal/watcher/pattern.go Added Windows volume name handling and platform-aware path separators
internal/watcher/pattern_test.go Added path normalization helpers and updated import path (needs fix)
internal/testext/exttest.go Added Windows-specific CGO flags (with hard-coded paths)
internal/testext/extensions.c Added frankenphp.h include for Windows compatibility
internal/testext/extension.h Added frankenphp.h include for Windows compatibility
internal/testcli/main.go Added //go:build !windows tag
internal/extgen/utils_test.go Added Windows-specific file permission handling
internal/extgen/stub_test.go Added Windows line ending handling (CRLF vs LF)
internal/cpu/cpu_windows.go Fixed unused parameter with underscore
internal/cpu/cpu_unix.go Added //go:build unix tag
types.h Reordered includes to have frankenphp.h first for Windows compatibility
phpmainthread.go Reordered includes for consistency
mercure.go Added frankenphp.h include
cgi.go Reordered includes for consistency
ext.go Uncommented frankenphp.h include
scaling.go Removed unused C imports
testdata/session.php Changed PHP_EOL to "\n" for consistency
go.mod, go.sum Updated watcher dependency to new module path
caddy/go.mod, caddy/go.sum Updated watcher dependency to new module path
caddy/frankenphp/main.go Removed direct caddy-cbrotli import
caddy/frankenphp/cbrotli.go Added conditional cbrotli import with //go:build !nobrotli
caddy/php-cli.go Added //go:build !windows tag

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@henderkes
Copy link
Contributor

henderkes commented Jan 12, 2026

Okay, I guess I figured it out? When I move it into the php folder it works, so I'm guessing php is trying to load something in the background but silently fails. Extensions would come to mind, but I'm not loading any.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows compatibility

3 participants